<body></body>
<script>
  let activeEffect = null
  const effectStack = [] //effect栈
  const bucket = new WeakMap()

  const data = {
    foo: 1
  }

  const obj = new Proxy(data, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, newVal) {
      target[key] = newVal
      trigger(target, key)
    }
  })

  function track(target, key) {
    if (!activeEffect) return
    let depMap = bucket.get(target)
    if (!depMap) {
      bucket.set(target, (depMap = new Map()))
    }

    let deps = depMap.get(key)
    if (!deps) {
      depMap.set(key, (deps = new Set()))
    }
    const effectSet = deps.add(activeEffect)
    activeEffect.deps.push(effectSet)
  }

  function trigger(target, key) {
    let depMap = bucket.get(target)
    if (!depMap) return
    const depsSet = depMap.get(key)
    const effectsToRun = new Set(depsSet)
    effectsToRun &&
      effectsToRun.forEach(effectFn => {
        if (effectFn?.options?.scheduler) {
          effectFn.options.scheduler(effectFn) //执行调度
        } else {
          effectFn()
        }
      })
  }

  function effect(fn, options) {
    // 这里的 effectFn是一个独立函数，就是每一次effect开辟一个新的空间，使用的是新的指针
    function effectFn() {
      cleanup(effectFn)
      activeEffect = effectFn

      effectStack.push(effectFn)
      fn()
      effectStack.pop(effectFn)
      activeEffect = effectStack[effectStack.length - 1]
    }
    effectFn.options = options
    effectFn.deps = [] //反向收集 effectSet的引用
    effectFn() // 执行副作用函数
  }
  function cleanup(effectFn) {
    effectFn.deps.forEach(depSet => {
      depSet.delete(effectFn) //遍历所有的 Set集合删除同一个副作用
    })
    effectFn.deps.length = 0
  }
  // 新增任务队列控制逻辑
  const jobQueue = new Set() // 任务队列（自动去重）
  let isFlushing = false // 队列刷新状态
  const p = Promise.resolve() // 微任务触发器

  // 1、刷新任务队列（通过微任务合并执行）
  function flushJob() {
    if (isFlushing) return
    isFlushing = true
    p.then(() => {
      jobQueue.forEach(job => job()) // 执行队列中的任务
    }).finally(() => {
      isFlushing = false
      jobQueue.clear() // 清空队列
    })
  }

  // 2、使用调度器控制执行时机
  effect(
    () => {
      console.log(obj.foo)
    },
    {
      scheduler(fn) {
        jobQueue.add(fn) // 将副作用加入队列
        flushJob() // 通过微任务执行队列
      }
    }
  )
  obj.foo++
  obj.foo++

  // 解释：
  // 这的 逻辑执行 obj.foo++ 执行的 set（）操作，set是同步改的值，
  // 副作用不需要执行多次，将 多次触发合并为一次执行（）,,
  // 在微任务阶段执行时，obj.foo 已经是最终值 3,  因此中间状态2不会输出。
  // 后面的副作用被合并执行了
</script>
